#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>
#include <unistd.h>

#include "../au.h"
#include "../common.h"
#include "afile.h"

enum {
	A_NULL,
	A_SIZE,
	A_ENC,
	A_RATE,
	A_CHAN
};

enum {
	AF_RIFF,
	AF_CHUNK_HDR,
	AF_CHUNK_BODY,
	AF_FMT_BODY,
	AF_DATA_BODY,
	AF_DATA_DONE,
	AF_EOF
};

struct afile {
	uint8_t		 buffer[26];
	size_t		 index;
	size_t		 sample;
	size_t		 byte;
	uint32_t	 encoding;
	uint32_t	 riff_size;
	uint32_t	 chunk_size;
	int		 state;
	int		 fd;
	int		 fmt;
	uint16_t	 fmt_encoding;
	uint16_t	 fmt_channels;
	uint32_t	 fmt_rate;
	uint32_t	 fmt_byterate;
	uint16_t	 fmt_block;
	uint16_t	 fmt_width;
	int		 data;
	off_t		 data_data;
};

const struct a_lookup stab[] = {
	{ "size", A_SIZE },
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "rate", A_RATE },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ NULL, A_NULL }
};

static uint16_t lget2(const void *p);
static uint32_t lget4(const void *p);

struct afile *
af_open(const char *file)
{
	struct afile *af;
	int fd;

	if (file == NULL)
		ERR(EINVAL, NULL);
	if ((fd = open(file, O_RDONLY)) < 0)
		return (NULL);
	if ((af = af_fdopen(fd)) == NULL)
		(void)close(fd);
	return (af);
}

struct afile *
af_fdopen(int fd)
{
	struct afile *a;

	if (fd < 0)
		ERR(EINVAL, NULL);
	if ((a = malloc(sizeof(struct afile))) == NULL)
		return (NULL);
	a->index = 0;
	a->byte = 0;
	a->sample = 0;
	a->state = AF_RIFF;
	a->fd = fd;
	a->fmt = 0;
	a->data = 0;
	return (a);
}

void
af_close(struct afile *a)
{

	if (a == NULL)
		return;
	(void)close(a->fd);
	free(a);
}

int
af_get(struct afile *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	if (!a->fmt)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_SIZE:
			if (!a->data)
				goto err;
			*va_arg(ap, uint32_t *) = a->chunk_size;
			break;
		case A_ENC:
			*va_arg(ap, uint32_t *) = a->encoding;

			break;
		case A_RATE:
			*va_arg(ap, uint32_t *) = a->fmt_rate;
			break;
		case A_CHAN:
			*va_arg(ap, uint32_t *) = a->fmt_channels;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
af_set(struct afile *a, const char *s, ...)
{

	ERR(EOPNOTSUPP, -1);
}

ssize_t
af_read(struct afile *a, void *b, size_t s)
{
	ssize_t r;

	if (a == NULL && b == NULL)
		ERR(EFAULT, -1);
	for (;;)
		switch (a->state) {
		case AF_RIFF:
			if ((r = nread(a->fd, a->buffer + a->index,
			    12 - a->index)) <= 0)
				return (r);
			if ((a->index += r) >= 12) {
				if (memcmp(a->buffer + 0, "RIFF", 4)
				    != 0 ||
				    memcmp(a->buffer + 8, "WAVE", 4)
				    != 0)
					ERR(EINVAL, -1);
				a->index = 0;
				a->byte += 12;
				a->riff_size = lget4(a->buffer + 4);
				if (a->riff_size < 42)
					a->state = AF_EOF;
				else
					a->state = AF_CHUNK_HDR;
			}
			break;
		case AF_CHUNK_HDR:
			if ((r = nread(a->fd, a->buffer + a->index,
			    8 - a->index)) <= 0)
				return (r);
			if ((a->index += r) >= 8) {
				a->index = 0;
				a->chunk_size = lget4(a->buffer + 4);
				if (a->chunk_size > a->riff_size ||
				    a->riff_size - a->chunk_size
				    < a->byte) {
					a->state = AF_EOF;
					return (0);
				}
				a->byte += a->chunk_size;
				if (memcmp(a->buffer, "fmt ", 4)
				    == 0) {
					if (a->fmt ||
					    a->chunk_size < 16 ||
					    a->chunk_size > 18)
						ERR(EINVAL, -1);
					a->state = AF_FMT_BODY;
				} else if (memcmp(a->buffer, "data", 4)
				    == 0) {
					if (a->data)
						ERR(EINVAL, -1);
					a->data = 1;
					a->data_data = lseek(a->fd, 0,
					    SEEK_CUR);
					a->state = AF_DATA_BODY;
				} else
					a->state = AF_CHUNK_BODY;
			}
			break;
		case AF_CHUNK_BODY:
			if ((r = nread(a->fd, b,
			    MIN(s, a->chunk_size - a->index))) <= 0)
				return (r);
			if ((a->index += r) >= a->chunk_size) {
				a->index = 0;
				a->state = AF_CHUNK_HDR;
			}
			break;
		case AF_FMT_BODY:
			if ((r = nread(a->fd, a->buffer + a->index,
			    a->chunk_size - a->index)) <= 0)
				return (r);
			if ((a->index += r) >= a->chunk_size) {
				a->index = 0;
				a->fmt = 1;
				a->fmt_encoding = lget2(a->buffer + 0);
				a->fmt_channels = lget2(a->buffer + 2);
				a->fmt_rate = lget4(a->buffer + 4);
				a->fmt_byterate = lget4(a->buffer + 8);
				a->fmt_block = lget2(a->buffer + 12);
				a->fmt_width = lget2(a->buffer + 14);
				a->state = AF_CHUNK_HDR;
				switch (a->fmt_encoding) {
				case 1:
					if (a->fmt_width <= 8)
						a->encoding = AU_PCM8;
					else if (a->fmt_width <= 16)
						a->encoding = AU_PCM16;
					else if (a->fmt_width <= 24)
						a->encoding = AU_PCM24;
					else if (a->fmt_width <= 32)
						a->encoding = AU_PCM32;
					else
						ERR(EINVAL, -1);
					break;
				case 3:
					switch (a->fmt_width) {
					case 32:
						a->encoding =
						    AU_FLOAT32;
						break;
					case 64:
						a->encoding =
						    AU_FLOAT64;
						break;
					default:
						ERR(EINVAL, -1);
					}
					break;
				case 6:
					a->encoding = AU_ALAW;
					break;
				case 7:
					a->encoding = AU_ULAW;
					break;
				default:
					ERR(EINVAL, -1);
				}
				if (a->fmt_rate == 0)
					ERR(ERANGE, -1);
			}
			break;
		case AF_DATA_BODY:
			s -= s % a->fmt_block;
			if ((r = nread(a->fd, b,
			    MIN(s, a->chunk_size - a->index))) <= 0)
				return (r);
			if ((a->index += r) >= a->chunk_size)
				a->state = AF_DATA_DONE;
			a->sample += r / a->fmt_block;
			(void)au_encbswap(a->encoding, b, r);
			return (r);
		case AF_DATA_DONE:
			/* FALLTHROUGH */
		case AF_EOF:
			return (0);
		}
	/* NOTREACHED */
}

struct timespec
af_seek(struct afile *a, struct timespec ts, int whence)
{
	const struct timespec tserr = { -1, -1 };
	int64_t s;
	int64_t m;
	off_t t;

	if (a == NULL)
		ERR(EINVAL, tserr);
	if (a->data_data <= 0)
		ERR(EOPNOTSUPP, tserr);
	m = a->chunk_size / a->fmt_block;
	s = ts.tv_sec * (int64_t)a->fmt_rate + ts.tv_nsec *
	    (int64_t)a->fmt_rate / INT64_C(1000000000);
	switch (whence) {
	case SEEK_SET:
		break;
	case SEEK_END:
		s = m - s;
		break;
	case SEEK_CUR:
		s += a->sample;
		break;
	default:
		ERR(EINVAL, tserr);
	}
	if (s < 0)
		s = 0;
	else if (s > m)
		s = m;
	if (s != a->sample) {
		if ((t = lseek(a->fd, (off_t)a->data_data +
		    s * (off_t)a->fmt_block, SEEK_SET)) < 0)
			return (tserr);
		s = (t - a->data_data) / a->fmt_block;
		a->sample = s;
	}
	ts.tv_sec = s / a->fmt_rate;
	ts.tv_nsec = (s % a->fmt_rate) * INT64_C(1000000000) /
	    a->fmt_rate;
	return (ts);
}

struct timespec
af_length(struct afile *a)
{
	const struct timespec tserr = { -1, -1 };
	struct timespec ts;
	uint64_t m;

	if (a == NULL)
		ERR(EINVAL, tserr);
	if (!a->data)
		ERR(EOPNOTSUPP, tserr);
	m = a->chunk_size / a->fmt_block;
	ts.tv_sec = m / a->fmt_rate;
	ts.tv_nsec = (uint64_t)(m % a->fmt_rate) *
	    UINT64_C(1000000000) / (uint64_t)a->fmt_rate;
	return (ts);
}

uint16_t
lget2(const void *p)
{
	const uint8_t *u = p;

	assert(p != NULL);
	return ((uint32_t)u[0] | ((uint32_t)u[1] << 8));
}

uint32_t
lget4(const void *p)
{
	const uint8_t *u = p;

	assert(p != NULL);
	return ((uint32_t)u[0] | ((uint32_t)u[1] << 8) |
	    ((uint32_t)u[2] << 16) | ((uint32_t)u[3] << 24));
}
